﻿/// <reference path="../lib/jquery-1.8.3.js" />
/// <reference path="../lib/underscore.js" />
/// <reference path="../lib/knockout-2.2.0.js" />
/// <reference path="model.js" />
/// <reference path="model.mapper.js" />
/// <reference path="dataservices.js" />
/// <reference path="config.js" />
/// <reference path="utils.js" />

/**
 @title:        datacontext
 @description:  ???
 @author:       Yuriy Shapovalov (shapovalov.yuri@gmail.com)
 */

define('datacontext',
    ['jquery', 'underscore', 'ko', 'model', 'model.mapper', 'dataservice', 'config', 'utils'],
    function ($, _, ko, model, modelmapper, dataservice, config, utils) {
        var
            logger = config.logger,
            getCurrentUserId = function () {
                // TODO: get this information from the session (or so on, depending of authentication method.
                return config.currentUser().id();
            },
            /**
             @description: Receive link to ViewModel and array of values from server. And map it.
            */
            itemsToArray = function (items, observableArray, filter, sortFunction) {
                if (!observableArray) return;

                // temporary array of items
                var underlyingArray = utils.mapMemoToArray(items); // copy items to underlayingArray

                // apply filters if exists
                if (filter) {
                    underlyingArray = _.filter(underlyingArray, function (o) {
                        var match = filter.predicate(filter, o);
                    });
                }
                // apply sorting if exists
                if (sortFunction) {
                    underlyingArray.sort(sortFunction);
                }
                // push data from the server to model which will be represented on the page
                observableArray(underlyingArray);
            },
            /**
             @description: Loop through the raw dto list and populate a dictionary of the items
             */
            mapToContext = function (dtoList, items, results, mapper, filter, sortFunction) {
                items = _.reduce(dtoList, function (memo, dto) { // reduce boils down a list of values into a single value. Memo is the initial state of the reduction
                    var id = mapper.getDtoId(dto); // get ID of the DTO object
                    var existingItem = items[id]; // check if we already have in items list an object with such ID
                    memo[id] = mapper.fromDto(dto, existingItem); // restore object from DTO, and receive model (for each particular component)
                    return memo; // assign memo to items
                }, {});
                itemsToArray(items, results, filter, sortFunction); // map this array of values (items) to ViewModel object
                // this probably does not require, because model mapping already completed in itemsToArray method. Or I'm wrong?
                return items;
            },

            /**
             @description: Controller for models, responsible for restoring models from service requests.
                            Call when project is initializing 
            */
            EntitySet = function (getFunction, mapper, nullo, updateFunction) {
                var items = {},
                    /**
                     @description: returns the model item produced by merging dto into context
                     */
                    mapDtoToContext = function (dto) {
                        debugger
                        var id = mapper.getDtoId(dto);
                        var existingItem = items[id];
                        items[id] = mapper.fromDto(dto, existingItem);
                        return items[id];
                    },
                    add = function (newObj) {
                        items[newObj.id()] = newObj;
                    },
                    removeById = function (id) {
                        delete items[id];
                    },
                    getLocalById = function (id) {
                        return !!id && !!items[id] ? items[id] : nullo;
                    },
                    getAllLocal = function () {
                        return utils.mapMemoToArray(items);
                    },
                    getData = function (options) {
                        return $.Deferred(function (def) {
                            var results = options && options.results,
                                sortFunction = options && options.sortFunction,
                                filter = options && options.filter,
                                forceRefresh = options && options.forceRefresh,
                                param = options && options.param,
                                getFunctionOverride = options && options.getFunctionOverride;

                            getFunction = getFunctionOverride || getFunction;

                            if (forceRefresh || !items || !utils.hasProperties(items)) {
                                getFunction({
                                    success: function (dtoList) {
                                        items = mapToContext(dtoList, items, results, mapper, filter, sortFunction);
                                        def.resolve(results);
                                    },
                                    error: function (response) {
                                        logger.error(config.toasts.errorGettingData);
                                        def.reject();
                                    }
                                }, param);
                            } else {
                                itemsToArray(items, results, filter, sortFunction);
                                def.resolve(results);
                            }
                        }).promise();
                    },
                    updateData = function (entity, callbacks) {
                        var entityJson = ko.toJSON(entity);

                        return $.Deferred(function (def) {
                            if (!updateFunction) {
                                logger.error('updateData method not implemented');
                                if (callbacks && callbacks.error) { callbacks.error(); }
                                def.reject();
                                return;
                            }

                            updateFunction({
                                success: function (response) {
                                    logger.success(config.toasts.savedData);
                                    entity.dirtyFlag().reset();
                                    if (callbacks && callbacks.success) { callbacks.success(); }
                                    def.resolve(response);
                                },
                                error: function (response) {
                                    logger.error(config.toasts.errorSavingData);
                                    if (callbacks && callbacks.error) { callbacks.error(); }
                                    def.reject(response);
                                    return;
                                }
                            }, entityJson);
                        }).promise();
                    };

                return {
                    mapDtoToContext: mapDtoToContext,
                    add: add,
                    getAllLocal: getAllLocal,
                    getLocalById: getLocalById,
                    getData: getData,
                    removeById: removeById,
                    updateData: updateData
                };
            },

            // repositories
            // Objects which will be exposed as a datacontext entity. Wrap functionality of managing data in models, receiving such data
            workspace = new EntitySet(dataservice.workspace.getWorkspace, modelmapper.workspace, model.Workspace.Nullo);
            activities = new EntitySet(dataservice.activities.getActivities, modelmapper.activities, model.Activity.Nullo)

        var datacontext = {
            workspace: workspace,
            activities: activities
        };

        model.setDataContext(datacontext);

        return datacontext;
    });